﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO.Ports;
using Interface;
using LogicAnalyzerCommon;

namespace AvrLogicAnalyzer
{
    internal class MessageReconstruct
    {
        private enum CommunicationState
        {
            WakeUpStart = 0,
            WakeupWaitForHello = 10,
            WakeupWaitForHelloRebound = 20,
            SetSendParams = 100,
            SetWaitForRegisters = 110,
            SamplingSendTrigger = 200,
            SamplingWaitForStart = 210,
            SamplingWaitForComplete = 220,
            SamplingRetrieveResultRegisters = 230,
            SamplingWaitForRegisters = 240,
            SamplingRequestSamples = 250,
            SamplingWaitForSamples = 260,
            SamplingProcessSamples = 270,
            SamplingDone = 999,
        }

        private enum MessageReconstructionState
        {
            WaitStartOfMessage = 0,
            WaitMessageType = 10,
            WaitParameters = 20,
            WaitChecksum = 30,
            MessageComplete = 99,
        }

        private enum DecompressState
        {
            WaitSequence0 = 0,
            WaitSequence1 = 1,
            WaitSequence2 = 2,
            Receive = 10,
            Got0xFE = 20,
            Got0xFE_0xFD = 30,
            Got0xFE_bb = 40,
        }

        private enum MessageType : byte
        {
            Sample = (byte)'S',
            Print  = (byte)'P',
            Hello  = (byte)'H',
            Memory = (byte)'M',
        }

        // Timeout in ms
        private const int ReceiveHelloMessageTimeout = 500;
        private const int ReceiveRegistersTimeout = 1500;
        private const int ReceiveSampleDataTimeout = 1500000;
        private const int PageSize = 256;

        private CommunicationState _communicationState;
        private DecompressState _decompressState;
        private MessageReconstructionState _messageReconstructionState;
        private MessageType _messageType;
        private int _currentParameter;
        private bool _messageReady;
        private bool _memoryMessage;
        private int _parameterCount;
        private int[] _parameters;
        private bool _addLastParam;
        private BackgroundWorker _backgroundWorker;
        private SerialPort _serialPort;
        private Stopwatch _stopwatch;
        private int _crc;
        private byte _decompressCount;
        private bool _decompressFlag;
        private IDictionary<string, string> _sampleSettings;
        private ILogicAnalyzer _logicAnalyzer;

        // r5 - sample rate in 10ns increments
        private long SampleRate
        {
            get
            {
                long sampleFrequency = _logicAnalyzer.GetSampleFrequency(_sampleSettings);
                return sampleFrequency; 
            }
        }

        private int PreSamplePages
        {
            get
            {
                byte preTriggerMemoryPercentage = AvrAnalyzer.GetPreTriggerMemoryPercentage(_sampleSettings);
                long sampleSize = AvrAnalyzer.GetSampleSize(_sampleSettings);

                int pageCount = (int)(sampleSize / PageSize);
                int preSamplePages = (pageCount * preTriggerMemoryPercentage) / 100;

                if (preTriggerMemoryPercentage < 100 && preSamplePages == pageCount)
                {
                    preSamplePages--;
                }

                if (preTriggerMemoryPercentage > 0 && preSamplePages == 0)
                {
                    preSamplePages++;
                }
                return preSamplePages;
            }
        }

        private int PostSamplePages
        {
            get
            {
                long sampleSize = AvrAnalyzer.GetSampleSize(_sampleSettings);

                int pageCount = (int)(sampleSize / PageSize);

                return pageCount - PreSamplePages;
            }
        }

        private TriggerMode TriggerMode { get { return AvrAnalyzer.GetTriggerMode(_sampleSettings); } }

        private byte AutoCompareTriggerLevel
        {
            get
            {
                return AvrAnalyzer.GetAutoCompareTriggerLevel(_sampleSettings);
            }
        }

        private byte AutoEdgeChannelIndex
        {
            get
            {
                return AvrAnalyzer.GetAutoEdgeChannelIndex(_sampleSettings);
            }
        }
        private byte GetAutoEdgeDirection
        {
            get
            {
                // todo: type conversion
                //return AvrAnalyzer.GetAutoEdgeDirection(_sampleSettings);
                return 0;
            }
        }

        internal byte[] Data { get; private set; }

        internal MessageReconstruct(BackgroundWorker backgroundWorker, SerialPort serialPort, ILogicAnalyzer logicAnalyzer, IDictionary<string, string> sampleSettings)
        {
            _parameters = new int[100];
            _backgroundWorker = backgroundWorker;
            _serialPort = serialPort;
            _stopwatch = new Stopwatch();
            _sampleSettings = sampleSettings;

            _logicAnalyzer = logicAnalyzer;
        }

        /// <summary>
        /// Signal that we are at the beginning of receiving a compressed message.
        /// </summary>
        private void EnterDecompressMode()
        {
            _decompressState = DecompressState.WaitSequence0;
            _decompressFlag = true;
        }

        /// <summary>
        /// Prepare for a new communcition sequence (which consists of multiple messages)
        /// </summary>
        internal void StartCommunication()
        {
            _communicationState = CommunicationState.WakeUpStart;
            _decompressFlag = false;
        }

        internal bool SamplingDone()
        {
            return _communicationState == CommunicationState.SamplingDone;
        }

        /// <summary>
        /// Rebuild message (type + 0..n * parameter) from byte stream
        /// Result is put into vars
        /// For msg type M (memory), params are put into alternative array
        /// When a message is complete, commsFSM is called.
        /// </summary>
        /// <param name="inByte"></param>
        public void ProcessBytes(byte[] byteStream, int byteCount)
        {
            for (int byteStreamIndex = 0; byteStreamIndex < byteCount; byteStreamIndex++)
            {
                //Console.WriteLine(string.Format("Acquire byte received: {0} 0x{0:x2} {1}", byteStream[byteStreamIndex], ((byteStream[byteStreamIndex] < 32) | (byteStream[byteStreamIndex] > 126)) ? '.' : (char)byteStream[byteStreamIndex]));

                if (_decompressFlag | byteStream[byteStreamIndex] == 0xFE)
                {
                    if (_decompressFlag == false)
                    {
                        EnterDecompressMode();
                    }
                    ProcessCompressedByte(byteStream[byteStreamIndex]);
                }
                else
                {
                    ProcessUncompressedByte(byteStream[byteStreamIndex]);
                }
            }
        }

        private void ProcessUncompressedByte(byte inByte)
        {
            //Console.WriteLine(string.Format("MsgReconstruct inbyte: {0} {1}", (char)inByte, inByte));

            switch (_messageReconstructionState)
            {
                case MessageReconstructionState.WaitStartOfMessage:
                    {
                        if (inByte == '[')
                        {
                            _messageReconstructionState = MessageReconstructionState.WaitMessageType;
                        }
                        break;
                    }
                case MessageReconstructionState.WaitMessageType:
                    {
                        Debug.Assert(Enum.IsDefined(typeof(MessageType), inByte));
                        _messageType = (MessageType)inByte;
                        if (_messageType == MessageType.Memory)  // alternative handling of memory storage.
                        {
                            _memoryMessage = true;
                        }
                        else
                        {
                            _memoryMessage = false;
                        }
                        _parameterCount = 0;
                        _currentParameter = 0;
                        _messageReconstructionState = MessageReconstructionState.WaitParameters;
                        break;
                    }
                case MessageReconstructionState.WaitParameters:
                    {
                        switch ((char)inByte)
                        {
                            case '[':   // restart
                                {
                                    _messageReconstructionState = MessageReconstructionState.WaitMessageType;
                                    break;
                                }
                            case '.':   // next param
                                {
                                    AddParameter(_currentParameter);
                                    _currentParameter = 0;
                                    _addLastParam = true;

                                    break;
                                }
                            case '*':   // prepare for checksum
                                {
                                    if (_addLastParam) AddParameter(_currentParameter);
                                    Console.WriteLine(string.Format("Received {0} params, wait for checksum", _parameterCount));
                                    _crc = 0;
                                    _messageReconstructionState = MessageReconstructionState.WaitChecksum;
                                    break;
                                }
                            case ']':   // message compete, finalise done at the end of this function
                                {
                                    AddParameter(_currentParameter);
                                    _messageReconstructionState = MessageReconstructionState.MessageComplete;
                                    break;
                                }
                            default:    // must be a hex param digit.
                                {
                                    _currentParameter = _currentParameter * 16 + Ascii2Nibble(inByte);
                                    break;
                                }
                        }
                        break;
                    }
                case MessageReconstructionState.WaitChecksum:
                    {
                        switch ((char)inByte)
                        {
                            case '[':   // restart
                                {
                                    _messageReconstructionState = MessageReconstructionState.WaitMessageType;
                                    break;
                                }
                            case ']':   // message compete, finalise done at the end of this function
                                {

                                    //Console.WriteLine(string.Format("crc received: {0}", _crc));

                                    if (_memoryMessage == true)
                                    {
                                        // we received analyser data
                                        //_data[5]++; // force crc error
                                        byte calculatedCrc = Data.Crc8Dallas();

                                        if (_crc != calculatedCrc)
                                        {
                                            throw new CrcException(string.Format("!!! CRC Error received: {0}, calculated: {1}", _crc, calculatedCrc));
                                        }
                                        else
                                        {
                                            Console.WriteLine(string.Format("crc OK (received: {0}, calculated: {1})", _crc, calculatedCrc));
                                        }
                                    }
                                    _messageReconstructionState = MessageReconstructionState.MessageComplete;
                                    break;
                                }
                            default:    // must be a hex param digit.
                                {
                                    _crc = _crc * 16 + Ascii2Nibble(inByte);

                                    break;
                                }
                        }
                        break;

                    }
                case MessageReconstructionState.MessageComplete:
                    {
                        // processing is done after this switch statement
                        break;
                    }
            }
            if (_messageReconstructionState == MessageReconstructionState.MessageComplete)
            {

                _messageReady = true;   // indicate message is ready 
                _messageReconstructionState = MessageReconstructionState.WaitStartOfMessage;    // wait for next message

                Console.WriteLine(string.Format("MsgReconstruct Message type: {0}, Paramcount: {1}, Param0: {2}, Param1: {3} , Param1: {4} ",
                    _messageType, _parameterCount, _parameters[0], _parameters[1], _parameters[2]));

                // call FSM to process message
                CommunicationsEngine();
            }
        }

        /// <summary>
        /// CommunicationsEngine() handles the sequence of communications.
        /// It is called when a message is received, but also frequently inbetween to
        /// facilitate timeout detection.
        /// 
        /// - First of all, Acquire() calls StartCommunication() that tells CommunicationsEngine()
        ///   to start a full communication sequence (from waking up the analyser, to configure, trigger, 
        ///   wait for samling to start, sampling to complete and to receive samples).
        /// - Regular calls from Acquire() to CommunicationsEngine() trigger the latter to
        ///   send a wake up message, detect timeouts etc.
        /// - When a byte is received from the analyser (usually a response to a message send by the FSM), 
        ///   Acquire() calls ProcessByte(), that composes a message from the bytes received. 
        /// - CommuncationsEngine() acts on these received messages.
        /// 
        /// So that's the 'external' part. The internal part is a bit complex. For the overview:
        /// ProcessUncompressedByte() handles all framing and everything requried for cleartext messages.
        /// ProcessCompressedByte() adds support for run lenght compression to ProcessUncompressedByte()
        /// AddParameter() collects parameters from both sources (compressed and uncompressed) and handles
        ///    both control messages (limited parameters, put into an array) and the memory messages 
        ///    (sample data, consisting of a large number of byte params, put into the designated data area).
        /// CommunicationsEngine() handles the full sequence of configure, sample and collect.
        /// 
        /// The *data* flow looks like:
        /// 
        /// ProcessByte ----> ProcessUncompressedByte            CommunicationsEngine
        ///    |                       |                                      ^
        ///    |                        -> |                                  |         
        ///    |                           |-> AddParameter ->(when msg done)-|
        ///     --ProcessCompressedByte--> |
        /// 
        /// - ProcessByte() splits the datastream for compressed and uncompressed bytes. Default (when  
        ///   starting), ProcessUncompressedByte() is called for each byte. 
        /// - Every time a complete parameter is received, ProcessUncompressedByte() calls AddParameter()
        ///   to add the new param to the message.
        /// - When complete, the ProcessUncompressedByte() sets _messageReady, which triggers
        ///   CommunicationsEngine() to process the message.
        /// - When the compression start flag is received, ProcessByte() diverts the datastream from 
        ///   from ProcessUncompressedByte() to ProcessCompressedByte().
        /// - For each decompressed byte (that is: result stream of ProcessCompressedByte()), AddParameter()
        ///   is called.
        /// - When the compression end flag is received, ProcessCompressedByte() flushes buffers
        ///   and the datastream from ProcessByte() is restored to ProcessUncompressedByte().
        ///   
        /// Consequences:
        /// - Only the param part of a message can be compressed. In other words: each message
        ///   starts and ends in text-mode.
        /// - In compressed mode, only bytes (8 bit) params can be passed. In text mode,
        ///   the protocol allows any size of params (multiple of 4 bits). The software 
        ///   currently supports params up to 32 bits (which is quite a lot of sample memory).
        ///   
        /// </summary>
        internal void CommunicationsEngine()
        {
            switch (_communicationState)
            {
                case CommunicationState.WakeUpStart:
                    {
                        // try to wake analyser
                        _serialPort.Write("~"); // reset analyser
                        _stopwatch.Reset();
                        _stopwatch.Start();
                        _communicationState = CommunicationState.WakeupWaitForHello;
                        break;
                    }
                case CommunicationState.WakeupWaitForHello:
                    {
                        if (_messageReady)
                        {
                            if (_messageType == MessageType.Hello)
                            {
                                _communicationState = CommunicationState.SetSendParams;
                            }
                            _messageReady = false; // we dealt with the message
                        }
                        if (_stopwatch.ElapsedMilliseconds > ReceiveHelloMessageTimeout)
                        {
                            Console.WriteLine("Timeout => Retry hello-message");
                            _serialPort.Write("~");
                            _stopwatch.Reset();
                            _stopwatch.Start();
                            _communicationState = CommunicationState.WakeupWaitForHelloRebound;

                        }
                        break;
                    }
                case CommunicationState.WakeupWaitForHelloRebound: // wait for hello message in the rebound
                    {
                        if (_messageReady)
                        {
                            if (_messageType == MessageType.Hello)
                            {
                                _communicationState = CommunicationState.SetSendParams;
                            }
                            _messageReady = false; // we dealt with the message
                        }
                        if (_stopwatch.ElapsedMilliseconds > ReceiveHelloMessageTimeout)
                        {
                            throw new TimeoutException("Communication timeout (no hello message from analyser)");
                        }
                        break;
                    }
                case CommunicationState.SetSendParams:
                    {
                        // (we received the hello message in the previous state and might want to check the version numbers here)

                        // send one message with 3 register values, starting with r5:
                        Console.WriteLine("Set analyser params");

                        Console.WriteLine("[r5.{0:x}", 10000000 / SampleRate);  // r5 - sample rate param in 10ns increments, SampleRate is in Hz
                        Console.WriteLine(".{0:x}", PostSamplePages);    // r6 - nr of 256 byte pages to sample after trigger
                        Console.WriteLine(".{0:x}]", PreSamplePages);    // r7 - nr of 256 byte pages to pre-sample

                        _serialPort.Write(string.Format("[r5.{0:x}", 10000000 /  SampleRate)); // r5 - sample rate in 10ns increments
                        _serialPort.Write(string.Format(".{0:x}", PostSamplePages));    // r6 - nr of 256 byte pages to sample after trigger
                        _serialPort.Write(string.Format(".{0:x}]", PreSamplePages));    // r7 - nr of 256 byte pages to pre-sample

                        // read all the settings registers back, so we can verify the settings.
                        _serialPort.Write("[p0.0a]");   // print 10 registers, starting at 0

                        _stopwatch.Reset();
                        _stopwatch.Start();
                        _communicationState = CommunicationState.SetWaitForRegisters;
                        break;
                    }

                case CommunicationState.SetWaitForRegisters:
                    {
                        if (_messageReady)
                        {
                            if (_messageType != MessageType.Print)
                            {
                                // not the message we expected.
                                throw new UnexpectedResponseException(string.Format("The analyzer responded with '{0}' message type while 'Print' (1) message type was expected.", Enum.GetName(typeof(MessageType),_messageType)));
                            }
                            _communicationState = CommunicationState.SamplingSendTrigger;
                            _messageReady = false; // we dealt with the message
                        }
                        else
                        {
                            if (_stopwatch.ElapsedMilliseconds > ReceiveRegistersTimeout)
                            {
                                throw new TimeoutException("Communication timeout 3 (no registers received from analyser)");
                            }
                        }
                        break;
                    }
                case CommunicationState.SamplingSendTrigger:
                    {
                        Console.WriteLine(String.Format("Start sampling mode {0:x}", TriggerMode));
                        //
                        // The sampling parameters are already set.
                        // Now we only have to instruct the analyser how to start, that is, how to trigger.
                        // The message has a fixed format with 3 paramters: 
                        //   The first param specifies the trigger method and two
                        //   additional params provide (method specific) settings. 
                        //
                        
                        // Byte 3: Trigger mode: 0 = autocmp, 1 = autoedge, 2 = manual (SLA file def, used in configuration panel)
                        // todo: the firmware supports 5 different trigger level; this could be added to the host app

                        // autocomp trigger - start when (pinc & param1) == param2
                        if (TriggerMode == 0)
                        {
                            // autocomp trigger 
                            _serialPort.Write(string.Format("[s{0:x}", 1));                         // target trigger method #define TRIGGER_CMP           1
                            _serialPort.Write(string.Format(".{0:x}", 255));                        // trigger param 1 mask out all irrelevant bits (in this case: none; could be extended in the host app)
                            _serialPort.Write(string.Format(".{0:x}]", AutoCompareTriggerLevel));   // trigger param 2 = start condition
                        }

                        // autoedge trigger - trigger when (portc & param1 == param2)
                        if ((int)TriggerMode == 1)
                        {
                            byte mask = (byte) (2 ^ AutoEdgeChannelIndex);
                            _serialPort.Write(string.Format("[s{0:x}", 1));                         // target trigger method #define TRIGGER_CMP   3  
                            _serialPort.Write(string.Format(".{0:x}", mask));                       // trigger param 1 - mask the bit
                            if (GetAutoEdgeDirection == 1)
                            {
                                _serialPort.Write(string.Format(".{0:x}]", mask));                  // trigger param 2 - (portc & param1 == param1)
                            }
                            else
                            {
                                _serialPort.Write(string.Format(".{0:x}]", 0));                     // trigger param 2 - (portc & param1 == 0)
                            }
                        }

                        // manual trigger - start now
                        if ((int)TriggerMode == 2)                                                  // trigger method   #define TRIGGER_MANUAL        0 
                        {
                            Console.WriteLine("Manual trigger mode");
                            Console.WriteLine("[s0]");                         // trigger method 0, no params
                            _serialPort.Write("[s0]");                         // trigger method 0, no params
                        }
                        _communicationState = CommunicationState.SamplingWaitForStart;
                        break;
                    }
                case CommunicationState.SamplingWaitForStart:
                    {
                        if (_messageReady)
                        {
                            if (_messageType == MessageType.Sample)
                            {
                                if ((_parameterCount != 1) | (_messageType != MessageType.Sample) | (_parameters[0] != 0x10))
                                {
                                    // not the message we expected.
                                    throw new UnexpectedResponseException(string.Format("The analyzer responded with '{0} {1:x}' while 'Sample 10' was expected.", Enum.GetName(typeof(MessageType), _messageType), _parameters[0]));
                                }
                                Console.WriteLine("Trigger done, analyser is collecting data (sampling)");
                                _communicationState = CommunicationState.SamplingWaitForComplete;
                            }
                            _messageReady = false; // we dealt with the message
                        }
                        break;
                    }
                case CommunicationState.SamplingWaitForComplete:
                    {
                        if (_messageReady)
                        {
                            if (_messageType == MessageType.Sample)
                            {
                                if ((_parameterCount != 1) | (_messageType != MessageType.Sample) | (_parameters[0] != 0x20))
                                {
                                    // not the message we expected.
                                    throw new UnexpectedResponseException(string.Format("The analyzer responded with '{0} {1:x}' while 'Sample 20' was expected.", Enum.GetName(typeof(MessageType), _messageType), _parameters[0]));
                                }
                                _communicationState = CommunicationState.SamplingRetrieveResultRegisters;
                            }
                            _messageReady = false; // we dealt with the message
                        }
                        break;
                    }
                case CommunicationState.SamplingRetrieveResultRegisters:
                    {
                        Console.WriteLine("Sampling done, now retrieve data");

                        // read all registers, so we know what happened
                        _serialPort.Write("[p0.0C]");   // print 12 registers, starting at 0

                        _stopwatch.Reset();
                        _stopwatch.Start();
                        _communicationState = CommunicationState.SamplingWaitForRegisters;
                        break;
                    }
                case CommunicationState.SamplingWaitForRegisters:
                    {
                        if (_messageReady)
                        {
                            if (_messageType != MessageType.Print)
                            {
                                // not the message we expected.
                                throw new UnexpectedResponseException(string.Format("The analyzer responded with a '{0}' message type  while a 'Print'(2) message type was expected.", Enum.GetName(typeof(MessageType), _messageType)));
                            }
                            for (int i = 0; i < 12; i++)
                            {
                                Console.WriteLine("Register {0}: {1}", i, _parameters[i]);
                            }


                            _communicationState = CommunicationState.SamplingRequestSamples;
                            _messageReady = false; // we dealt with the message
                        }
                        else
                        {
                            if (_stopwatch.ElapsedMilliseconds > ReceiveRegistersTimeout)
                            {
                                throw new TimeoutException("Communication timeout 3 (no registers received from analyser)");
                            }
                        }
                        break;
                    }
                case CommunicationState.SamplingRequestSamples: // calculate which part of the sample memory we need & ask for it.
                    {
                        // we request the sample data by specifying the startaddress and the number of bytes (both in bytes
                        // not pages). It is no problem if the start point is beyond the end point - the analyser will just
                        // wrap around from the end to position 0.
                        int startPage = _parameters[10] - _parameters[7];               // trigger page - presample page
                        if (startPage < 0) startPage += _parameters[1];                 // add memory size for wrap around
                        if (startPage >= _parameters[1]) startPage -= _parameters[1];   // wrap around the other way

                        int startPosition = startPage * 256 + _parameters[11];          // trigger point - presample size

                        int byteCount = (_parameters[6] + _parameters[7]) * 256;        // sample size + presample size


                        //nrBytes = 128000; // to limit test size

                        Data = new byte[byteCount];

                        // enable compression (just omit this message if you want cleartext)
                        _serialPort.Write("[r4.1]");


                        Console.WriteLine("Get {0} bytes, starting from position {1}", byteCount, startPosition);
                        Console.WriteLine("[m{0:x}.{1:x}]", startPosition, byteCount);
                        _serialPort.Write(string.Format("[m{0:x}.{1:x}]", startPosition, byteCount));

                        _stopwatch.Reset();
                        _stopwatch.Start();
                        _communicationState = CommunicationState.SamplingWaitForSamples;
                        break;
                    }
                case CommunicationState.SamplingWaitForSamples:
                    {
                        if (_messageReady)
                        {
                            if (_messageType != MessageType.Memory)
                            {
                                // not the message we expected.
                                throw new UnexpectedResponseException(string.Format("The analyzer responded with '{0}' message type while 'Memory' message type was expected.", Enum.GetName(typeof(MessageType), _messageType)));
                            }
                            _communicationState = CommunicationState.SamplingProcessSamples;
                            _messageReady = false; // we dealt with the message
                        }
                        else
                        {
                            if (_stopwatch.ElapsedMilliseconds > ReceiveSampleDataTimeout)
                            {
                                throw new TimeoutException("Communication timeout 4 (no sample data received from analyser)");
                            }
                        }
                        break;
                    }
                case CommunicationState.SamplingProcessSamples:
                    {
                        Console.WriteLine(string.Format("Sample data retrieved in {0} ms, process it & done", _stopwatch.ElapsedMilliseconds));

                        _communicationState = CommunicationState.SamplingDone;
                        break;
                    }
            }
        }

        /// <summary>
        /// Process a received byte of a compressed messgae.
        /// This routine handles runlenght compression and interprets the output
        /// as single byte paramters. Parameters are passed to MessageAddParam().
        /// </summary>
        /// <param name="inByte"></param>
        internal void ProcessCompressedByte(byte inByte)
        {
            //Console.WriteLine(string.Format("decompress state: {0}", decompressState));

            switch (_decompressState)
            {
                case DecompressState.WaitSequence0:
                    {
                        if (inByte == 0xFE)
                        {
                            _decompressState = DecompressState.WaitSequence1;
                            break;
                        }
                        _decompressFlag = false;
                        break;
                    }
                case DecompressState.WaitSequence1:
                    {
                        if (inByte == 0xFD)
                        {
                            _decompressState = DecompressState.WaitSequence2;
                            break;
                        }
                        _decompressFlag = false;
                        _decompressState = DecompressState.WaitSequence0;      // we should not receive an FE without FD 00 ...
                        break;
                    }
                case DecompressState.WaitSequence2:
                    {
                        if (inByte == 0x00)
                        {
                            _decompressState = DecompressState.Receive;
                            break;
                        }
                        _decompressFlag = false;
                        _decompressState = DecompressState.WaitSequence0;      // we should not receive an FE without FD 00 ...
                        break;
                    }
                case DecompressState.Receive: // idle
                    {
                        if (inByte == 0xFE)
                        {
                            _decompressState = DecompressState.Got0xFE;
                            break;
                        }
                        // process char
                        AddParameter(inByte);
                        break;
                    }
                case DecompressState.Got0xFE:
                    {
                        if (inByte == 0xFF) // FE FF -> FE
                        {
                            AddParameter(0xFE);
                            _decompressState = DecompressState.Receive;
                            break;
                        }
                        if (inByte == 0xFD) // FE FD
                        {
                            _decompressState = DecompressState.Got0xFE_0xFD;
                            break;
                        }
                        if (inByte == 0x00) // FE 00 -> reserved
                        {
                            // error 
                            _decompressState = DecompressState.Receive;
                            break;
                        }

                        // sequence of bytes, this is the count byte.
                        _decompressCount = inByte;
                        _decompressState = DecompressState.Got0xFE_bb;
                        break;
                    }

                case DecompressState.Got0xFE_0xFD:
                    {
                        if (inByte == 0x00) // FE FD 00 -> start of compression - ignore
                        {
                            _decompressState = DecompressState.Receive;
                            break;
                        }
                        if (inByte == 0x01) // FE FD 01 -> End of compression.
                        {
                            _decompressState = 0;
                            _decompressFlag = false;
                            _addLastParam = false; // indicate the next star must not trigger param add
                            break;
                        }
                        // else: sequence of FE
                        for (byte i = 0; i < inByte; i++)
                        {
                            // feed sequence of bytes to MsgReconstruct()
                            AddParameter(0xFE);
                        }

                        _decompressState = DecompressState.Receive;
                        break;
                    }

                case DecompressState.Got0xFE_bb:    // last byte of 'FE nn vv  = sequence of nn bytes with value vv (vv != 0xFE, 0 < nn < 253)'
                    {
                        for (byte i = 0; i < _decompressCount; i++)
                        {
                            // feed sequence of bytes to MsgReconstruct()
                            AddParameter(inByte);
                        }
                        _decompressState = DecompressState.Receive;
                        break;
                    }
            }
        }

        /// <summary>
        /// convert ascii hex char ('0'...'9', 'a'...'f', 'A'...'F') to it's value (0-15)
        /// </summary>
        /// <param name="InByte"></param>
        /// <returns></returns>
        private static int Ascii2Nibble(byte inByte)
        {
            Debug.Assert((inByte >= '0' && inByte <= '9') ||
                (inByte >= 'a' && inByte <= 'f') ||
                (inByte >= 'A' && inByte <= 'F'));

            inByte -= 0x30;                     // ascii 0 becomes 0
            if (inByte > 9) inByte -= 7;        // ascii A becomes 10
            if (inByte > 16) inByte -= 0x20;    // ascii a becomes 10
            return inByte;
        }

        private void AddParameter(int param)
        {
            //Console.WriteLine(string.Format("MsgAddParam nr: {0}, value 0x{1:x2}", msgR.ParamCount, Param));

            if (_memoryMessage == false)
            {
                _parameters[_parameterCount] = param;
            }
            else
            {
                Data[_parameterCount] = (byte)param;
            }
            _parameterCount++;
            if (_memoryMessage == true)
            {
                // update progress bar when we receive a memory message
                _backgroundWorker.ReportProgress((100 * _parameterCount) / Data.Length);
            }
        }
    }
}
